package plp.orientadaObjetos2.comando;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

import plp.expressions2.expression.Id;
import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.orientadaObjetos1.comando.Comando;
import plp.orientadaObjetos1.excecao.declaracao.ClasseJaDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ClasseNaoDeclaradaException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ObjetoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoJaDeclaradoException;
import plp.orientadaObjetos1.excecao.declaracao.ProcedimentoNaoDeclaradoException;
import plp.orientadaObjetos1.excecao.execucao.EntradaInvalidaException;
import plp.orientadaObjetos1.excecao.execucao.EntradaNaoFornecidaException;
import plp.orientadaObjetos1.expressao.Expressao;
import plp.orientadaObjetos1.expressao.valor.ValorString;
import plp.orientadaObjetos1.memoria.AmbienteCompilacaoOO1;
import plp.orientadaObjetos1.memoria.AmbienteExecucaoOO1;
import plp.orientadaObjetos1.memoria.DefClasse;
import plp.orientadaObjetos1.memoria.colecao.ListaValor;
import plp.orientadaObjetos1.util.TipoPrimitivo;
import plp.orientadaObjetos2.Programa;
import plp.orientadaObjetos2.declaracao.ConstrutorNaoDeclaradoException;
import plp.orientadaObjetos2.memoria.ContextoCompilacaoOO2;
import plp.orientadaObjetos2.memoria.FuncaoJaDeclaradaException;
import plp.orientadaObjetos2.parser.OO2Parser;
import plp.orientadaObjetos2.parser.ParseException;

public class Exec implements Comando {


	/**
	 * Nome completo da classe
	 */
	private Expressao stringComando;

	/**
	 * A expressao significa um caminho ou uma string para ser avaliada?
	 */
	private boolean isFilePath;

	public Exec(Expressao fileName) {
		this.stringComando = fileName;
	}

	public Exec(Expressao fileName, boolean isFilePath) {
		this.stringComando = fileName;
		this.isFilePath = isFilePath;
	}

	public AmbienteExecucaoOO1 executar(AmbienteExecucaoOO1 ambiente)
			throws VariavelJaDeclaradaException, VariavelNaoDeclaradaException,
			ObjetoJaDeclaradoException, ObjetoNaoDeclaradoException,
			ProcedimentoNaoDeclaradoException,
			ProcedimentoJaDeclaradoException, ClasseJaDeclaradaException,
			ClasseNaoDeclaradaException, EntradaInvalidaException {
		try {
			// Compile o arquivo novo
			OO2Parser parser = new OO2Parser(getStringStream(ambiente));
			Programa programa = parser.processaEntrada();

			// Crie um contexto de compilacao sandbox com as classes atuais do ambiente de execucao
			HashMap<Id, DefClasse> mapDefClasse = ambiente.getMapDefClasse();
			ContextoCompilacaoOO2 contextoSandbox = new ContextoCompilacaoOO2(new ListaValor());
			for (Entry<Id, DefClasse> entry : mapDefClasse.entrySet()) {
				contextoSandbox.mapDefClasse(entry.getKey(), entry.getValue());
			}
			// Verifique a tipagem do novo arquivo e popule o contexto sandbox com as novas definicoes
			programa.checaTipo(contextoSandbox);

			// Traga as novas definicoes para o contexto de excecucao atual
			Set<Entry<Id, DefClasse>> mapaIdClasses = contextoSandbox.getClasses().entrySet();
			for (Entry<Id, DefClasse> entry : mapaIdClasses) {
				Id novoId = entry.getKey();
				DefClasse definicaoClasse = entry.getValue();
				try{
					if(ambiente.getDefClasse(novoId) == null){
						ambiente.mapDefClasse(novoId, definicaoClasse);
					}
				} catch(ClasseNaoDeclaradaException cnde){
					ambiente.mapDefClasse(novoId, definicaoClasse);
				}
			}
		} catch (FileNotFoundException e) {
			throw new RuntimeException("Classe " + this.stringComando + " nao encontrada", e);
		} catch (ParseException e) {
			throw new RuntimeException("Classe " + this.stringComando + " mal formada", e);
		} catch (EntradaNaoFornecidaException e) {
			throw new RuntimeException("Classe " + this.stringComando + " mal formada", e);
		} catch (ConstrutorNaoDeclaradoException e) {
			throw new RuntimeException("Classe " + this.stringComando + " mal formada", e);
		} catch (FuncaoJaDeclaradaException e) {
			throw new RuntimeException("Classe " + this.stringComando + " mal formada", e);
		}
		return ambiente;
	}

	private Reader getStringStream(AmbienteExecucaoOO1 ambiente)
			throws FileNotFoundException, ObjetoNaoDeclaradoException,
			ClasseNaoDeclaradaException {
		Reader stream;
		if(this.isFilePath){
			stream = new FileReader(resolvePath(ambiente));
		} else {
			stream = new StringReader(getCommandString(ambiente));
		}
		return stream;
	}

	private File resolvePath(AmbienteExecucaoOO1 ambiente) throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException, ObjetoNaoDeclaradoException, ClasseNaoDeclaradaException {
		String path = getCommandString(ambiente);
		File file = new File(path); 
		if(!file.exists()){
			file = null;
			String classpath = System.getenv("CLASSPATH");
			if(classpath != null) {
				String[] paths = classpath.split(System.getProperty("path.separator"));
				for (String p : paths) {
					file = new File(p + System.getProperty("file.separator") + path);
					if(file.exists()){
						break;
					}
				}				
			}
		}
		if(file == null){
			throw new RuntimeException("Impossivel localizar arquivo " + path);
		}
		return file;
	}

	private String getCommandString(AmbienteExecucaoOO1 ambiente)
			throws ObjetoNaoDeclaradoException, ClasseNaoDeclaradaException {
		return ((ValorString) this.stringComando.avaliar(ambiente)).valor();
	}

	public boolean checaTipo(AmbienteCompilacaoOO1 ambiente)
			throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException,
			ProcedimentoNaoDeclaradoException,
			ProcedimentoJaDeclaradoException, ClasseJaDeclaradaException,
			ClasseNaoDeclaradaException {
		return this.stringComando.checaTipo(ambiente) && this.stringComando.getTipo(ambiente).equals(new TipoPrimitivo(TipoPrimitivo.STRING));
	}

}
